#pragma once

#include "types.h"
#include "dirs.hpp"
#include <cstring>
#include <ctime>
#include <list>
#include <memory>

namespace Trade {

struct M15Timer
{
  bool to = false; // timer out
  bool in_range = false;
  bool fto = false;
  time_t t = 0;

  bool in_timer_range(const time_t t_now) noexcept
  {
    if (t) {
      auto now = int(t_now / 60);
      auto far = int(t / 60);
      bool in = (now >= far - 15 && now <= far + 15);
      if (in != in_range) {
        in_range = in;
        return in_range;
      }
    }
    return false;
  }

  bool far_timer_out(const time_t t_now) noexcept
  {
    if (t && !fto) {
      auto now = int(t_now / 60);
      auto far = int(t / 60);
      fto = (now >= far + 15);
      if (fto)
        return true;
    }
    return false;
  }

  bool is_timer_outed(const time_t t_now) noexcept
  {
#if (!SHMEM_READER)
    timer_out(t_now);
#endif

    return to;
  }

  void set_timer(const time_t tm) noexcept
  {
    to = false;
    t = tm;
  }

  M15Timer() noexcept
    : to(false)
    , t(0)
  {
  }

  M15Timer(const time_t tm) noexcept
    : to(false)
    , t(tm)
  {
  }

private:
  bool timer_out(const time_t t_now) noexcept
  {
    if (t && !to) {
      to = (t_now >= t);
      if (to)
        return true;
    }
    return false;
  }
};

struct TimeTurn
{
  dir_t dir = 0;
  bool im_large = false;
  int t_delta = 0;
  int64_t my_idx = 0;
  time_t my_ts = 0;
  price_t my_hp = 0;
  price_t hp_delta = 0;
  M15Timer t_up;
  M15Timer t_dn;

  TimeTurn() = default;

  TimeTurn(const dir_t d,
           const int64_t idx,
           const time_t ts,
           const price_t hpx,
           const time_t tup,
           const time_t tdn) noexcept
    : dir(d)
    , t_delta(0)
    , my_idx(idx)
    , my_ts(ts)
    , my_hp(hpx)
    , hp_delta(0)
    , t_up(tup)
    , t_dn(tdn)
  {
  }

  const TimeTurn& operator=(const TimeTurn& tt) noexcept
  {
    memcpy((void*)(this), &tt, sizeof(TimeTurn));
    return tt;
  }

  void reset() noexcept { memset((void*)(this), 0, sizeof(TimeTurn)); }
  bool to_up(const time_t tm) noexcept;
  bool to_dn(const time_t tm) noexcept;
  void print(const int64_t idx,
             const time_t ts,
             const price_t hpx) const noexcept;
  uint8_t check_reached(const time_t tm) noexcept;
  bool strict_dir(const dir_t d) const noexcept;
  bool good_dir(const dir_t d) const noexcept;

  bool is_timer_outed(const dir_t d) const noexcept
  {
    return (d > 0) ? (t_up.to) : (t_dn.to);
  }

  bool large_time(const dir_t d, const time_t ts) const noexcept;
  bool i_am_important(const price_t hpx) const noexcept;
  time_t fact_dir(const dir_t d) const noexcept;
  dir_t strong_one_dir(const time_t ts) const noexcept;
  bool more_than(const time_t ts,
                 const dir_t d,
                 const int minutes) const noexcept;
  int durantion(const time_t ts) const noexcept { return int(ts - my_ts) / 60; }
};

using TimeTurnPtr = std::shared_ptr<TimeTurn>;
using TimeTurnPtrList = std::list<TimeTurnPtr>;
using TimeTurnPair = std::pair<const TimeTurnPtr, const TimeTurnPtr>;
using TimeTurnFour = std::array<const TimeTurnPtr, 4>;
using TtlIterator = TimeTurnPtrList::iterator;
using CttlIterator = TimeTurnPtrList::const_iterator;

enum TWO_STRONG_TIMER : uint8_t
{
  TWO_STRONG_TIMER_NONE = 0,
  TWO_STRONG_TIMER_UP = 1,
  TWO_STRONG_TIMER_DN = 2,
  TWO_STRONG_TIMER_CONF_G2_UP = 3,
  TWO_STRONG_TIMER_CONF_G2_DN = 4,
  TWO_STRONG_TIMER_CONFUSED = 5,
};

enum class TURN_TIME_STATE : uint8_t
{
  UNKONW = 0,
  UP = 1,
  DN = 2,
  BOTH_GOOD = 3,
  BOTH_ZERO = 4,
};

struct TimeStatus
{
  bool good = false;
  uint8_t cno = 0;
};

struct TimeGood
{
  TimeStatus ts_up;
  TimeStatus ts_dn;

  void reset() noexcept { memset((void*)(this), 0, sizeof(TimeGood)); }
};


/*
struct AbstractTurns {
  virtual ~AbstractTurns() {}
  virtual void print(const int64_t idx,
             const int64_t ts,
             const price_t hpx) const noexcept = 0;
  virtual time_t get_weak_timer(const dir_t d, const time_t t_now) noexcept = 0;
  virtual time_t get_strong_timer(const dir_t d, const time_t t_now) noexcept = 0;
  virtual DIR_DREAMER_TYPE get_first_dir_type() const noexcept = 0;
};
*/

struct Turns5
{
public:
  TimeTurn tt_1;
  TimeTurn tt_2;
  TimeTurn tt_3;
  TimeTurn tt_up;
  TimeTurn tt_dn;
public:
  void print(const int64_t idx,
             const int64_t ts,
             const price_t hpx) const noexcept;
  time_t get_weak_timer(const dir_t d, const time_t t_now) noexcept;
  time_t get_strong_timer(const dir_t d, const time_t t_now) noexcept;
  DIR_DREAMER_TYPE get_first_dir_type() const noexcept;

  void set_tt_1(TimeTurn* t) { tt_1 = *t; }
  void set_tt_2(TimeTurn* t) { tt_2 = *t; }
  void set_tt_3(TimeTurn* t) { tt_3 = *t; }
  void set_tt_up(TimeTurn* t) { tt_up = *t; }
  void set_tt_dn(TimeTurn* t) { tt_dn = *t; }
};

struct Turns
{
private:
  int _deep = 1000;
  TURN_TIME_STATE tts = TURN_TIME_STATE::UNKONW;

public:
  TimeGood tg;
  TimeTurnPtrList tl;
  TimeTurnPtr tt_up;
  TimeTurnPtr tt_dn;
  TimeTurnPtr tt_cur_large;

public:
  bool new_turn(const dir_t d,
                const int64_t idx,
                const time_t ts,
                const price_t hpx,
                const time_t tup,
                const time_t tdn) noexcept;

  TimeTurnPtr first() const noexcept { return tl.front(); }
  TimeTurnPtr second() const noexcept
  {
    if (tl.size() < 2) {
      return nullptr;
    } else {
      auto i = tl.cbegin();
      ++i;
      return (*i);
    }
  }
  void init_large_turn(const TimeTurnPtr& turn, const time_t ts) noexcept;
  std::pair<uint8_t, uint8_t> check_turn_time_out(const time_t ts) noexcept;
  bool change_turn(const time_t ts) noexcept;
  bool timer_out(const dir_t d, const time_t ts) const noexcept;
  bool is_timer_outed(const dir_t d, const time_t ts) const noexcept;
  const TimeTurnPtr& get_oppo_turn(const dir_t d) const noexcept
  {
    return (d > 0) ? tt_dn : tt_up;
  }
  const TimeTurnPtr& get_this_turn(const dir_t d) const noexcept
  {
    return (d > 0) ? tt_up : tt_dn;
  }
  bool this_nto_oppo_nto_but_no_restrict(const time_t ts) const noexcept
  {
    const dir_t d = first()->dir;
    return (!is_timer_outed(d, ts) && !is_timer_outed(-d, ts) &&
            !get_oppo_turn(d)->strict_dir(-d));
  }

  void print(const int64_t idx,
             const int64_t ts,
             const price_t hpx) const noexcept;
  case_t get_time_out_case(const time_t t_now, const dir_t d) const noexcept;

  time_t get_strong_timer(const dir_t d, const time_t t_now) noexcept;
  time_t get_weak_timer(const dir_t d, const time_t t_now) noexcept;
  time_t get_long_turn_timer(const dir_t d) noexcept;
  time_t get_long_oppo_turn_timer(const dir_t d) noexcept;
  time_t get_near_turn_timer(const dir_t d) noexcept;
  time_t get_near_oppo_turn_timer(const dir_t d) noexcept;
  time_t get_minimam_timer(const dir_t d, const time_t t_now) noexcept;
  bool get_long_turn_in_time_range(const dir_t d) noexcept;
  bool get_long_oppo_turn_in_time_range(const dir_t d) noexcept;
  bool get_near_turn_in_time_range(const dir_t d) noexcept;
  bool get_near_oppo_turn_in_time_range(const dir_t d) noexcept;

  bool very_good() const noexcept;
  bool good_time_changed(const time_t ts, const dir_t d) noexcept;
  TURN_TIME_STATE retrive_turn_time_status() const noexcept;
  bool turn_time_status_changed(const time_t ts,
                                const dir_t big_reverse) noexcept;
  TURN_TIME_STATE get_turn_time_status() const noexcept;
  bool near_turn_is_good(const time_t ts, const dir_t d) const noexcept;
  bool all_to(const time_t ts, const dir_t d) const noexcept;
  bool all_no_to(const time_t ts, const dir_t d) const noexcept;
  bool add_big_reverse_needle_status(const dir_t d) noexcept;

  dir_t get_timer_dir() const noexcept;
  dir_t up_to_now(const time_t tm_now,
                  const price_t hp_now,
                  price_t& large_to_new,
                  price_t& large_to_new_with_large,
                  price_t& oppo_to_new,
                  price_t& oppo_to_new_with_large,
                  price_t& delta_first_to_now) const noexcept;
  dir_t space_dir(const time_t t_now, const price_t hp_now) const noexcept;
  bool most_weak_time(const time_t t_now) const noexcept;

  void conveter_to_5(Turns5& t5) const noexcept;
  DIR_DREAMER_TYPE get_first_dir_type() const noexcept;

  const price_t MIN_TURN_LARGE_DELTA = 4.5;
};

} // namespace Trade